package ci.web.util.file;

import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.HashMap;
import java.util.Map;

/**
 * 文件系统监控器
 * @author zhangheng
 */
public class FsWatcher implements Runnable{
    private static InternalLogger logger = InternalLoggerFactory.getInstance(FsWatcher.class);
    
	private final Path dirPath;  
	private final Map<WatchKey, Path> keyMap = new HashMap<WatchKey, Path>();
	private WatchService watchService;
	private volatile boolean running;
	private FsHandler handler;
	public FsWatcher(String dirPath) {
		this(dirPath, null);
	}
	public FsWatcher(String dirPath, FsHandler handler) {
		this.dirPath = Paths.get(dirPath);
		this.handler = handler;
	}
	/**
	 * 添加变化handler
	 * @param h
	 */
	public void handler(FsHandler h){
		this.handler = h;
	}
	/**
	 * 侦听变化的handler
	 * @return
	 */
	public FsHandler handler(){
		return handler;
	}
	/**
	 * 停止侦听
	 */
	public void stop(){
		if(running){
			running = false;
			keyMap.clear();
			if(watchService!=null){
				try{
				watchService.close();
				}catch(Exception e){}
				watchService = null;
			}
		}
	}
	/**
	 * 开始侦听
	 */
	public void start(){
		Thread thread = new Thread(this);
		thread.setName("FsWather");
//		thread.setDaemon(true);
		thread.start();
	}
	
	public void run(){
		if(running)return;
		stop();
		try {
			watchService = FileSystems.getDefault().newWatchService();
			Files.walkFileTree(dirPath, new FsSimpleFileVisitor(watchService, keyMap));
		} catch (IOException e) {
			watchService = null;
			logger.error("", e);
			return;
		}
		running = true;
		while(running){
			 // retrieve and remove the next watch key  
			try {
				WatchKey key = watchService.take();
				if(key==null)continue;
		        // get list of pending events for the watch key  
		        for (WatchEvent<?> watchEvent : key.pollEvents()) {  
		            // get the kind of event (create, modify, delete)  
		            final Kind<?> kind = watchEvent.kind();  
		            // get the filename for the event  
		            @SuppressWarnings("unchecked")
					WatchEvent<Path> watchEventPath = (WatchEvent<Path>) watchEvent;  
		            Path eventPath = watchEventPath.context();  
		            Path realPath = keyMap.get(key).resolve(eventPath);
		            // handle OVERFLOW event  
		            if (kind == StandardWatchEventKinds.OVERFLOW) {  
		                continue;  
		            }  
		            if(kind == StandardWatchEventKinds.ENTRY_CREATE){  
		            	if(realPath.toFile().isDirectory()){
		            		try {
								Files.walkFileTree(realPath, new FsSimpleFileVisitor(watchService, keyMap));
							} catch (IOException e) {
							}
		            	}
		            	if(handler!=null){
		            		handler.onCreate(realPath.toFile());
		            	}
		            }  
		            if(kind == StandardWatchEventKinds.ENTRY_MODIFY){  
		            	if(handler!=null){
		            		handler.onModify(realPath.toFile());
		            	}
		            }  
		            if(kind == StandardWatchEventKinds.ENTRY_DELETE){  
		            	if(handler!=null){
		            		handler.onDelete(realPath.toFile());
		            	}
		            }  
		            // print it out  
//		            System.out.println(kind + " -> " + eventPath + " "+eventPath.getFileName().toFile().getName()); 
//		            System.out.println(realPath);
		        }  
		        // reset the keyf  
		        boolean valid = key.reset();  
		        // exit loop if the key is not valid (if the directory was  
		        // deleted, for  
		        if (!valid) {  
		        	Path p = keyMap.remove(key);
		        	if(p!=null && p.equals(dirPath)){
		        		break;
		        	}
		        }  
			} catch (InterruptedException e) {
				break;
			}  
		}
		stop();
	}
	
}
